namespace ScrumSprintMonitor.Implementation.Configuration
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Diagnostics;
	using System.Globalization;
	using System.IO;
	using System.Linq;
	using System.Reflection;
	using System.Text;
	using System.Windows.Media;

	using Infrastructure;
	using Infrastructure.Configuration;

	#endregion

	public class StreamBasedPresentationConfiguration : DataStreamObjectSerializer,
	                                                    IPresentationConfiguration
	{
		#region Private Fields

		private readonly RemainingWorkStatusPresentation[] ranges;

		#endregion

		public StreamBasedPresentationConfiguration(IDataStreamProvider streamProvider)
			: base(streamProvider)
		{
			var defaultRanges = new List<RemainingWorkStatusPresentation>();
			AddDefaultRange(RemainingWorkStatus.NoWorkLeft, Colors.Transparent, Colors.White, 0.0, defaultRanges);
			AddDefaultRange(RemainingWorkStatus.SeverelyUnderassigned, Colors.MidnightBlue, Colors.White, 0.5, defaultRanges);
			AddDefaultRange(RemainingWorkStatus.Underassigned, Color.FromRgb(0, 0, 200), Colors.White, 0.8, defaultRanges);
			AddDefaultRange(RemainingWorkStatus.Normal, Color.FromRgb(0, 200, 0), Colors.White, 1.0, defaultRanges);
			AddDefaultRange(RemainingWorkStatus.Warning, Colors.Orange, Colors.White, 1.05, defaultRanges);
			AddDefaultRange(RemainingWorkStatus.Overassigned, Color.FromRgb(225, 0, 0), Colors.White, 1.20, defaultRanges);
			AddDefaultRange(RemainingWorkStatus.SeverelyOverassigned, Color.FromRgb(200, 0, 0), Colors.White, double.PositiveInfinity, defaultRanges);

			this.ranges = defaultRanges.ToArray();

			EnsureObjectIsRead();
		}

		private static void AddDefaultRange(RemainingWorkStatus status, Color backgroundColor, Color foregroundColor, double upperLimit,
		                                    ICollection<RemainingWorkStatusPresentation> defaultRanges)
		{
			defaultRanges.Add(new RemainingWorkStatusPresentation(status, defaultRanges.LastOrDefault())
			                  	{
			                  		BackgroundColor = backgroundColor,
			                  		ForegroundColor = foregroundColor,
			                  		UpperLimit = upperLimit
			                  	});
		}

		protected override void ReadObjectFromStream()
		{
			GuardDisposed();

			using (var stream = this.streamProvider.OpenStream(FileAccess.Read))
			{
				using (var reader = new StreamReader(stream))
				{
					string version = null;

					for (;;)
					{
						string line = reader.ReadLine();
						if (line == null)
						{
							break;
						}

						line = line.TrimStart();
						if (line.StartsWith("'"))
						{
							continue;
						}

						var tokens = line.Split(new[] {'='});
						string propertyName = tokens[0].Trim();
						string propertyValue = tokens[1].Trim();

						if (propertyName != "Version" && string.IsNullOrEmpty(version))
						{
							throw new InvalidOperationException("Configuration must start first with the Version property");
						}

						switch (propertyName)
						{
							case "Version":
								version = propertyValue;
								break;
							case "RemainingWorkStatusRanges":
							{
								var readRanges = propertyValue.Split(new[] {';'});
								foreach (string rangeValues in readRanges)
								{
									var values = rangeValues.Split(new[] {','});
									var status = (RemainingWorkStatus) Enum.Parse(typeof(RemainingWorkStatus), values[0]);
									var config = GetRemainingWorkStatusPresentationConfig(status);
									if (values[1] != "Infinity")
									{
										config.UpperLimit = double.Parse(values[1], NumberFormatInfo.InvariantInfo);
									}
									config.BackgroundColor = (Color) ColorConverter.ConvertFromString(values[2]);
									config.ForegroundColor = (Color) ColorConverter.ConvertFromString(values[3]);
								}
								break;
							}
							default:
								// Ignore unknown values
								break;
						}
					}
				}
			}

			AsyncRaisePropertyChanged(null); // This may get called from a non-UI thread
		}

		#region Implementation of IPresentationConfiguration

		public IRemainingWorkStatusPresentationConfig GetRemainingWorkStatusPresentationConfig(RemainingWorkStatus status)
		{
			int index = (int) status;
			if (index < 0 || index >= this.ranges.Length)
			{
				throw new ArgumentException("status");
			}

			return this.ranges[index];
		}

		public bool IsReadOnly
		{
			[DebuggerStepThrough]
			get
			{
				GuardDisposed();

				return this.streamProvider.IsReadOnly;
			}
		}

		public void Save()
		{
			GuardDisposed();

			try
			{
				FreezeStreamChangeNotifications();

				using (var stream = this.streamProvider.OpenStream(FileAccess.Write))
				{
					using (var writer = new StreamWriter(stream))
					{
						var toleranceRanges = new StringBuilder();
						for (var status = RemainingWorkStatus.NoWorkLeft; status <= RemainingWorkStatus.SeverelyOverassigned; ++status)
						{
							var configuration = GetRemainingWorkStatusPresentationConfig(status);
							if (toleranceRanges.Length > 0)
							{
								toleranceRanges.Append("; ");
							}
							toleranceRanges.AppendFormat(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", status, configuration.UpperLimit, configuration.BackgroundColor,
							                             configuration.ForegroundColor);
						}

						WriteProperty(writer, "Version", FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).FileVersion);
						WriteProperty(writer, "RemainingWorkStatusRanges", toleranceRanges.ToString());
					}
				}
			}
			finally
			{
				UnfreezeStreamChangeNotifications();
			}
		}

		private static void WriteProperty(TextWriter writer, string propertyName, string propertyValue)
		{
			writer.WriteLine("{0}= {1}", propertyName, propertyValue);
		}

		#endregion
	}

	public class RemainingWorkStatusPresentation : IRemainingWorkStatusPresentationConfig
	{
		private readonly IRemainingWorkStatusPresentationConfig previousEntry;
		private readonly RemainingWorkStatus status;
		private Color backgroundColor;
		private Color foregroundColor;
		private double upperLimit;

		public RemainingWorkStatusPresentation(RemainingWorkStatus status, IRemainingWorkStatusPresentationConfig previousEntry)
		{
			this.status = status;
			this.previousEntry = previousEntry;
			if (this.previousEntry != null)
			{
				this.previousEntry.PropertyChanged += OnPreviousEntryPropertyChanged;
			}
		}

		#region IRemainingWorkStatusPresentationConfig Members

		public RemainingWorkStatus Status
		{
			[DebuggerStepThrough]
			get { return this.status; }
		}

		public double LowerLimit
		{
			[DebuggerStepThrough]
			get { return (this.previousEntry != null ? this.previousEntry.UpperLimit : 0.0); }
			set
			{
				if (this.previousEntry != null && value != LowerLimit)
				{
					this.previousEntry.UpperLimit = value;
				}
			}
		}

		public double UpperLimit
		{
			[DebuggerStepThrough]
			get { return this.upperLimit; }
			set
			{
				if (value != this.upperLimit)
				{
					if (value < LowerLimit)
					{
						throw new ArgumentOutOfRangeException("value", "The lower limit cannot be higher than the upper limit.");
					}

					double previousValue = this.upperLimit;

					this.upperLimit = value;

					try
					{
						OnPropertyChanged("UpperLimit");
					}
					catch (ArgumentOutOfRangeException)
					{
						UpperLimit = previousValue;
					}
				}
			}
		}

		public Color BackgroundColor
		{
			[DebuggerStepThrough]
			get { return this.backgroundColor; }
			set
			{
				if (value != this.backgroundColor)
				{
					this.backgroundColor = value;

					OnPropertyChanged("BackgroundColor");
				}
			}
		}

		public Color ForegroundColor
		{
			[DebuggerStepThrough]
			get { return this.foregroundColor; }
			set
			{
				if (value != this.foregroundColor)
				{
					this.foregroundColor = value;

					OnPropertyChanged("ForegroundColor");
				}
			}
		}

		#endregion

		#region INotifyPropertyChanged members

		public event PropertyChangedEventHandler PropertyChanged;

		public void OnPropertyChanged(string propertyName)
		{
			if (PropertyChanged != null)
			{
				PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
			}
		}

		#endregion

		private void OnPreviousEntryPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (UpperLimit < LowerLimit)
			{
				throw new ArgumentOutOfRangeException("e", "The lower limit cannot be higher than the upper limit.");
			}

			OnPropertyChanged("LowerLimit");
		}
	}
}